package frame;

import test.Dog;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * IOC容器
 * 1.扫描指定包下的所有类
 * 2.根据类上的注解，判断是否需要创建Bean
 * 3.创建Bean
 * 4.自动注入字段
 * 5.调用初始化方法
 * 6.将Bean放入IOC容器中
 * 7.提供获取Bean的方法
 * 8.提供获取Bean的方法，支持根据类型获取Bean
 * @author 不鄢
 */
public class ApplicationContext {

    /**
     * Bean的生命周期
     * 1.实例化：创建Bean的实例对象，通过配置文件获取Bean的定义，通过反射创建Bean的实例对象
     * 2.属性注入：将Bean的属性注入到Bean的实例对象中，通过配置文件获取Bean的属性，通过反射设置Bean的属性
     * 3.初始化： aware接口 BeanPostProcessor 初始化 BeanPostprocessor
     * 4.销毁
     * <p>
     * Bean的作用域
     * 1.单例
     * 2.原型
     * 3.请求
     * 4.会话
     * 5.全局会话
     * <p>
     * Bean的生命周期方法
     * 1.@PostConstruct
     * 2.@PreDestroy
     * 3.实现InitializingBean接口
     * 4.实现DisposableBean接口
     * 5.自定义方法
     * <p>
     * Bean的依赖注入方式
     * 1.构造器注入
     * 2.设值注入
     * 3.接口注入
     * 4.自动注入
     * 5.工厂方法注入
     * 6.自定义注入
     * <p>
     * Bean的生命周期管理
     * 1.手动管理
     * 2.自动管理
     * 3.容器管理
     *
     */


    private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();

    private Map<String,Object> ioc = new HashMap<>();

    //存储创建但是还未进行注入和方法初始化调用的bean
    //建立二级缓存，以解决循环依赖的问题。实际上的SpringIOC容器有三级缓存机制，解决代理调用情况下的循环依赖
    private Map<String,Object> loadingIoc = new HashMap<>();

    private List<BeanPostProcessor> postProcessors = new ArrayList<>();

    public ApplicationContext(String packageName) throws IOException {
        initContext(packageName);
    }

    public Object getBean(String name){
        if(name == null){
            return null;
        }
        Object bean = this.ioc.get(name);
        if(bean != null){
            return bean;
        }
        if(beanDefinitionMap.containsKey(name)){
            return createBean(beanDefinitionMap.get(name));
        }
        return null;
    }

    public <T> T getBean(Class<T> beanType){
        /**
         * isAssignableFrom() 是 java.lang.Class 类中的一个方法，用于判断一个类是否可以赋值给另一个类
         * 如果调用该方法的 Class 对象所表示的类或接口，是参数 cls 所表示的类或接口的超类或超接口，或者两者是同一个类或接口，则返回 true；否则返回 false。
         */
        String beanName = this.beanDefinitionMap.values().stream()
                .filter(bd -> beanType.isAssignableFrom(bd.getType()))
                .map(BeanDefinition::getName)
                .findFirst()
                .orElse(null);
        return (T) getBean(beanName);
    }

    public <T> List<T> getBeans(Class<T> beanType){
        return this.beanDefinitionMap.values().stream()
                .filter(bd -> beanType.isAssignableFrom(bd.getType()))
                .map(BeanDefinition::getName)
                .map(this::getBean)
                .map(bean -> (T)bean)
                .toList();

    }

    /**
     * 初始化上下文
     * @param packageName
     */
    public void initContext(String packageName) throws IOException {

        /**
         * 对应Bean的生命周期中，bean的实例化，配置文件读取获取BeanDefinition，反射创建实例对象
         * 这里不仅实例化了常规的bean，而且initBeanPostProcessor者进行了实例化创建
         */

        //先加载所有的BeanDefinition，用于自动注入字段时，判断是否存在bean
        scanPackage(packageName).stream().filter(this::scanCreate).forEach(this::wrapper);
        initBeanPostProcessor();
        beanDefinitionMap.values().forEach(this::createBean);
    }

    private void initBeanPostProcessor() {
        beanDefinitionMap.values().stream()
                .filter(bd->BeanPostProcessor.class.isAssignableFrom(bd.getType()))
                .map(this::createBean)
                .map((bean) -> (BeanPostProcessor) bean)
                .forEach(postProcessors::add);
    }

    // bean 的创建
    protected Object createBean(BeanDefinition beanDefinition){
        String name = beanDefinition.getName();
        if(ioc.containsKey(name)){
            return ioc.get(name);
        }
        if(loadingIoc.containsKey(name)){
            return loadingIoc.get(name);
        }
        return doCreateBean(beanDefinition);
    }


    private Object doCreateBean(BeanDefinition beanDefinition) {
        Constructor<?> constructor = beanDefinition.getConstructor();
        Object bean = null;
        try {
            // 通过构造器获取bean实例 （反射创建bean实例对象）
            bean = constructor.newInstance();
            //存放不完全的bean
            loadingIoc.put(beanDefinition.getName(),bean);

            // 自动注入字段
            autowiredBean(bean,beanDefinition);

            // bean的初始化
            bean = initializeBean(bean,beanDefinition);

            loadingIoc.remove(beanDefinition.getName());
            ioc.put(beanDefinition.getName(),bean);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return bean;
    }

    private Object initializeBean(Object bean, BeanDefinition beanDefinition) throws InvocationTargetException, IllegalAccessException {

        for(BeanPostProcessor postProcessor : postProcessors){
            bean = postProcessor.beforeInitializeBean(bean,beanDefinition.getName());
        }

        // 获取到postConstructMethod并在(创建实例之后)初始化时进行调用
        Method postConstructMethod = beanDefinition.getPostConstructMethod();
        if(postConstructMethod != null){
            postConstructMethod.invoke(bean);
        }

        for(BeanPostProcessor postProcessor : postProcessors){
            bean = postProcessor.afterInitializeBean(bean,beanDefinition.getName());
        }

        return bean;
    }

    private void autowiredBean(Object bean, BeanDefinition beanDefinition) throws IllegalAccessException {
        for (Field autowiredField : beanDefinition.getAutowiredFields()) {
            autowiredField.setAccessible(true);
            autowiredField.set(bean,getBean(autowiredField.getType()));
        }
    }

    /**
     * 为类的对象创造对应的BeanDefinition
     * 防止类的名字重复
     * @param type
     * @return
     */
    protected BeanDefinition wrapper(Class<?> type){
        BeanDefinition beanDefinition = new BeanDefinition(type);
        if(beanDefinitionMap.containsKey(beanDefinition.getName())){
            throw new RuntimeException("bean name is over");
        }
        beanDefinitionMap.put(beanDefinition.getName(),beanDefinition);
        return beanDefinition;
    }

    /**
     * 判断是否是我们需要加载的类
     * 而且方便子类重写我们，从而指定加载其它的类
     * @param type
     * @return
     */
    protected boolean scanCreate(Class<?> type){
        return type.isAnnotationPresent(Component.class);
    }

    /**
     * 扫描指定包下的所有的类
     * @param packageName
     * @return
     * @throws IOException
     */
    private List<Class<?>> scanPackage(String packageName) throws IOException {
        List<Class<?>> classList = new ArrayList<>();

        /**
         * 1.this 指代当前 ApplicationContext 类的实例。
         * getClass() 是 Object 类的方法，所有 Java 类都继承自 Object 类，所以可以调用该方法。此方法会返回表示当前对象所属类的 Class 对象。
         * 比如在 ApplicationContext 类的实例中调用 this.getClass()，就会返回 ApplicationContext 类对应的 Class 对象。
         * 2. getClassLoader()
         * getClassLoader() 是 Class 类的方法，它会返回加载当前类的类加载器。
         * 类加载器的作用是将类的字节码文件加载到 Java 虚拟机（JVM）中。在 Java 里，有不同类型的类加载器，例如引导类加载器、扩展类加载器和应用程序类加载器等。
         * 这里调用 getClassLoader() 得到的是加载 ApplicationContext 类的类加载器。
         * 3. getResource(packageName.replace(".", File.separator))
         * getResource() 是 ClassLoader 类的方法，它的作用是根据给定的资源名称来查找资源，并返回一个 URL 对象，该对象指向该资源的位置。
         * packageName 是传入 scanPackage 方法的参数，代表要扫描的包名，一般是类似 com.example.package 这样的字符串。
         * packageName.replace(".", File.separator) 会把包名里的点号（.）替换成文件系统的分隔符。在不同操作系统中，文件系统的分隔符不同，像在 Windows 系统中是反斜杠（\），在 Linux 和 macOS 系统中是正斜杠（/）。
         * 例如，若 packageName 是 com.example.package，在 Windows 系统下，替换后的字符串就会是 com\example\package；在 Linux 或 macOS 系统下，替换后的字符串则是 com/example/package。
         * 类加载器会在其搜索路径里查找与这个替换后的字符串对应的资源，若找到就返回一个指向该资源的 URL 对象；若没找到，就返回 null。
         */
        URL resource = this.getClass().getClassLoader().getResource(packageName.replace(".", File.separator));

        //不做空判断
        String filePath = resource.getFile();
        // 处理 Windows 路径前导斜杠问题
        if (File.separatorChar == '\\' && filePath.startsWith("/")) {
            filePath = filePath.substring(1);
        }
        Path path = Path.of(filePath);
        //以访问者模式，遍历路径下的所有的文件
        Files.walkFileTree(path,new SimpleFileVisitor<>(){
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Path absolutePath = file.toAbsolutePath();
                if(absolutePath.toString().endsWith(".class")){
                    //System.out.println(absolutePath);
                    //根据文件名获取到类的全类名
                    String replaceStr = absolutePath.toString().replace(File.separator,".");
                    int packageIndex = replaceStr.indexOf(packageName);
                    String className = replaceStr.substring(packageIndex,replaceStr.length()-".class".length());
                    //System.out.println(className);
                    try {
                        classList.add(Class.forName(className));
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
                //表示任何情况都可以继续访问文件
                return FileVisitResult.CONTINUE;
            }
        });
        return classList;
    }
}
